#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <string.h>

#include "lcd_dev.h"
#include "lcd_drv.h"

static char *fbp = 0;
static unsigned long startx, starty;
static unsigned long endx, endy;
static unsigned long vinfo_xres, vinfo_yres;
static unsigned long bits_per_pixel;

// Init fb0 device, return 0 if success, -1 if failed
int lcd_init(void)
{
    int fbfd = 0;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    long int screensize = 0;

    // Open the file for reading and writing
    fbfd = open("/dev/fb0", O_RDWR);
    if (!fbfd) {
        printf("Error: cannot open framebuffer device.\n");
        return -1;
    }
    printf("The framebuffer device was opened successfully.\n");

    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
        printf("Error reading fixed information.\n");
        return -1;
    }

    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
        printf("Error reading variable information.\n");
        return -1;
    }


    vinfo_xres = finfo.line_length / 4; // vinfo.xres;
    vinfo_yres = vinfo.yres;
    bits_per_pixel = vinfo.bits_per_pixel;
    startx = (vinfo_xres - SCREEN_WIDTH * SCREEN_PIXEL_SIZE) / 2 - (vinfo_xres - vinfo.xres) / 2;
    starty = (vinfo_yres - SCREEN_HEIGHT * SCREEN_PIXEL_SIZE) / 2;
    endx = startx + SCREEN_WIDTH * SCREEN_PIXEL_SIZE;
    endy = starty + SCREEN_HEIGHT * SCREEN_PIXEL_SIZE;

    // Figure out the size of the screen in bytes
    screensize = vinfo.yres_virtual * finfo.line_length;
    // screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

    printf("%ldx%d, %dbpp, line_length = %d size = %ld\n",
           vinfo_xres, vinfo.yres,
           vinfo.bits_per_pixel, finfo.line_length,
           screensize);
    printf("startx = %ld, starty = %ld\n", startx, starty);
    printf("vinfo_xres = %ld, vinfo_yres = %ld\n", vinfo_xres, vinfo_yres);
    printf("yoffset = %d, xoffset = %d\n", vinfo.yoffset, vinfo.xoffset);

    // Map the device to memory
    fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
    if ((intptr_t)fbp == -1) {
        printf("Error: failed to map framebuffer device to memory.\n");
        return -1;
    }
    printf("The framebuffer device was mapped to memory successfully.\n");

    // Clear the screen
    memset(fbp, COLOR_DARKGRAY, screensize);

    // Set the screen to white
    for (int row = starty; row < endy; row++) {
        for (int col = startx; col < endx; col++) {
            int location = (col + vinfo_xres * row) * 4;
            // *((unsigned long *)(fbp + location)) = COLOR_LIGHTGRAY;
            *((unsigned int *)(fbp + location)) = COLOR_WHITE;
        }
    }
    return 0;
}

int lcd_fill_rect(int x, int y, int width, int height, int color)
{
    unsigned int *p = (unsigned int *)fbp;

    for (int j = y; j < y + height; j++) {
        for (int i = x; i < x + width; i++) {
            p[i + vinfo_xres * j] = color;
            // *((volatile unsigned int *)(fbp + location)) = 0xffff0000;
        }
    }
    return 0;
}

int print_buf(char *buf, int len)
{
    unsigned long *p = (unsigned long *)buf;
    for (int i = 0; i < len; i++) {
        printf("%08x ", p[i]);
        if (i % 16 == 15) {
            printf("\n");
        }
    }
    printf("\n");
    return 0;
}

int lcd_test(void)
{
    printf("unsigned long = %ld\n", sizeof(unsigned long));
    printf("unsigned int = %ld\n", sizeof(unsigned int));
    print_buf(fbp, 100);
    lcd_fill_rect(0, 0, 10, 2, COLOR_WHITE);
    print_buf(fbp, 100);
    exit(0);
    return 0;
}

// put a pixel on the screen, create by lcd_init at position (x, y) with color
// use fbp create by lcd_init, in center of the screen SCREEN_WIDTH * SCREEN_HEIGHT
int lcd_put_pixel(int x, int y, int color)
{
    unsigned int *p = (unsigned int *)fbp;
    int x1 = startx + x * SCREEN_PIXEL_SIZE;
    int x2 = x1 + SCREEN_PIXEL_SIZE;

    int y1 = starty + y * SCREEN_PIXEL_SIZE;
    int y2 = y1 + SCREEN_PIXEL_SIZE;

    for (int j = y1; j <= y2; j++) {
        for (int i = x1; i < x2; i++) {
            p[i + vinfo_xres * j] = color;
        }
    }
    return 0;
}
